using PaddleOCRSharp;

namespace OcrTest;

public partial class Form1 : Form
{
    readonly PaddleOCREngine _engin;

    OCRResult? _ocrResult;

    // private string? _imageFile=null;
    private Image? _image = null;
    private Point _mouseDownPoint;
    private readonly string _title = "文本识别";

    public Form1()
    {
        InitializeComponent();
        pictureBox1.MouseWheel += pictureBox1_MouseWheel;
        this.Text = _title;
        pictureBox1.AllowDrop = true;

        OCRModelConfig? config = null;
        var ocrParam = new OCRParameter();
        _engin = new PaddleOCREngine(config, ocrParam);
    }


    private void SetImage(string? fileName = null)
    {
        Image image;
        if (!string.IsNullOrEmpty(fileName))
        {
            this.Text = _title + " - " + fileName;
            _image = Image.FromFile(fileName);
            image = (Image)_image.Clone();
        }
        else
        {
            if (_image == null)
            {
                MessageBox.Show(@"Please select an image file");
                return;
            }

            image = pictureBox1.Image;
        }


        var scaleFactor = 1.0 * image.Width / image.Height;
        if (image.Width > image.Height)
        {
            pictureBox1.Width = panel1.Width;
            pictureBox1.Height = (int)(pictureBox1.Width / scaleFactor);
            pictureBox1.Location = new Point(0, (panel1.Height - pictureBox1.Height) / 2);
        }
        else
        {
            pictureBox1.Height = panel1.Height;
            pictureBox1.Width = (int)(pictureBox1.Height * scaleFactor);
            pictureBox1.Location = new Point((panel1.Width - pictureBox1.Width) / 2);
        }

        pictureBox1.Image = image;
        pictureBox1.Refresh();
    }

    private void BtnOpenFile(object sender, EventArgs e)
    {
        var openFileDialog = new OpenFileDialog();
        DialogResult dialogResult = openFileDialog.ShowDialog();
        if (dialogResult != DialogResult.OK)
        {
            return;
        }

        SetImage(openFileDialog.FileName);
        btnDetect.PerformClick();
    }


    private void btnReset_Click(object sender, EventArgs e)
    {
        pictureBox1.Image = (Image)_image?.Clone();
        SetImage();
    }

    private void btnRotate_Click(object sender, EventArgs e)
    {
        if (_image == null)
        {
            return;
        }


        Control? control = sender as Control;
        switch (control?.Tag)
        {
            case "left":
                _image.RotateFlip(RotateFlipType.Rotate270FlipNone);
                pictureBox1.Image?.RotateFlip(RotateFlipType.Rotate270FlipNone);
                break;
            case "right":
                _image.RotateFlip(RotateFlipType.Rotate90FlipNone);
                pictureBox1.Image?.RotateFlip(RotateFlipType.Rotate90FlipNone);
                break;
        }

        SetImage();
    }

    private void btnDetect_Click(object sender, EventArgs e)
    {
        btnReset.PerformClick();
        if (pictureBox1.Image == null)
        {
            return;
        }

        btnDetect.Enabled = false;
        richTextBox1.Clear();
        textBox2.Clear();
        this.Cursor = Cursors.AppStarting;
        progressBar1.Show();
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        var task = Task.Run(() => { _ocrResult = _engin.DetectText(pictureBox1.Image); }, tokenSource.Token);
        Thread.Sleep(100);
        tokenSource.Cancel();


        task.ContinueWith(_ =>
        {
            this.Cursor = Cursors.Default;
            progressBar1.Hide();
            if (_ocrResult == null) return;
            richTextBox1.Clear();
            List<Array> polyList = new List<Array>();
            using (Graphics g = Graphics.FromImage(pictureBox1.Image ?? throw new InvalidOperationException()))
            {
                Pen pen = new Pen(Color.Red, 2);
                foreach (var item in _ocrResult.TextBlocks)
                {
                    polyList.Add(item.BoxPoints.Select(x => new PointF(x.X, x.Y)).ToArray());
                    richTextBox1.AppendText($"{polyList.Count.ToString().PadLeft(2, '0')}：{item.Text}\n");
                    g.DrawPolygon(pen, item.BoxPoints.Select(x => new PointF(x.X, x.Y)).ToArray());
                    g.DrawString(polyList.Count.ToString().PadLeft(2, '0'), new Font("Arial", 12), Brushes.Red,
                        new PointF(item.BoxPoints.First().X, item.BoxPoints.First().Y));
                }
            }

            btnDetect.Enabled = true;
            pictureBox1.Refresh();
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    private void pictureBox1_DragDrop(object sender, DragEventArgs e)
    {
        if (e.Data?.GetData(DataFormats.FileDrop, false) is string[] data)
        {
            // pictureBox1.Image = Image.FromFile(data.First());
            SetImage(data.First());

            btnDetect.PerformClick();
        }
    }

    private void pictureBox1_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data != null && e.Data.GetDataPresent(DataFormats.FileDrop))
            e.Effect = DragDropEffects.Move;
        else
            e.Effect = DragDropEffects.None;
    }

    private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Left)
        {
            return;
        }

        if (_ocrResult == null || _mouseDownPoint != e.Location)
        {
            return;
        }

        var p = ConvertPointToImagePoint(e.X, e.Y);

        var i = 0;
        foreach (var textBlock in _ocrResult.TextBlocks)
        {
            label2.Text = @$"x:{p.X}， y:{p.Y}";
            i++;
            // if (p.X > textBlock.BoxPoints[0].X && p.X < textBlock.BoxPoints[2].X && p.Y > textBlock.BoxPoints[0].Y &&
            //     p.Y < textBlock.BoxPoints[2].Y)
            if (CheckPoint(p, textBlock.BoxPoints))
            {
                label2.Text +=
                    $@"，1：{textBlock.BoxPoints[0].X}/{textBlock.BoxPoints[0].Y},  2：{textBlock.BoxPoints[2].X}/{textBlock.BoxPoints[2].Y}";
                label1.Text = "序号：" + i.ToString().PadLeft(2, '0');
                textBox2.Text = textBlock.Text;
                if (!string.IsNullOrEmpty(textBlock.Text))
                {
                    Clipboard.SetText(textBlock.Text);
                }

                break;
                // MessageBox.Show(textBlock.Text);
            }

            label1.Text = "序号：";
            textBox2.Text = "";
        }
    }

    private Point ConvertPointToImagePoint(int x, int y)
    {
        double scale;
        Point p = new Point();
        double subX = 0;
        double subY = 0;
        if (pictureBox1.Image.Width > pictureBox1.Image.Height)
        {
            scale = 1.0 * (pictureBox1.Image.Width) / pictureBox1.Width;
            subY = (pictureBox1.Height * scale - pictureBox1.Image.Height) / 2;
        }
        else
        {
            scale = 1.0 * pictureBox1.Image.Height / pictureBox1.Height;

            subX = (pictureBox1.Width * scale - pictureBox1.Image.Width) / 2;
        }

        p.X = (int)(x * scale - subX);
        p.Y = (int)(y * scale - subY);

        return p;
    }

    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Right)
        {
            return;
        }
        // Console.WriteLine(e.X+","+e.Y+"\n");
        // Console.WriteLine(pictureBox1.Location.X +","+pictureBox1.Location.Y);

        int deltaX = e.X - _mouseDownPoint.X;
        int deltaY = e.Y - _mouseDownPoint.Y;

        int newX = pictureBox1.Location.X + deltaX;
        int newY = pictureBox1.Location.Y + deltaY;
        Console.WriteLine("1:" + newX + "," + newY);
        if (panel1.Width > pictureBox1.Width)
        {
            newX = Math.Max(0, Math.Min(newX, panel1.Width - pictureBox1.Width));
            newY = Math.Max(0, Math.Min(newY, panel1.Height - pictureBox1.Height));
            Console.WriteLine("2:" + newX + "," + newY);
        }
        else
        {
            newX = Math.Min(Math.Max(panel1.Width - pictureBox1.Width, newX), 0);
            newY = Math.Min(Math.Max(panel1.Height - pictureBox1.Height, newY), 0);
        }


        pictureBox1.Location = new Point(newX, newY);
    }

    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
        _mouseDownPoint = e.Location;
        Console.WriteLine(_mouseDownPoint.X + "," + _mouseDownPoint.Y);
    }

    private void pictureBox1_MouseWheel(object? sender, MouseEventArgs e)
    {
        if (pictureBox1.Image == null)
        {
            return;
        }

        double step = 1.2;
        int width = pictureBox1.Width;
        int height = pictureBox1.Height;
        int px = e.X - pictureBox1.Location.X;
        int py = e.Y - pictureBox1.Location.Y;
        if (e.Delta > 0)
        {
            pictureBox1.Height = (int)(pictureBox1.Height * step);
            pictureBox1.Width = (int)(pictureBox1.Width * step);
            int x = (int)(px * (step - 1));
            int y = (int)(py * (step - 1));
            //pictureBox1.Location =new Point(pictureBox1.Location.X-x,pictureBox1.Location.Y-y);
            pictureBox1.Location = new Point(pictureBox1.Location.X - (int)(e.X * (step - 1)),
                pictureBox1.Location.Y - (int)(e.Y * (step - 1)));
            // Console.WriteLine($"x:{x},y:{y}");
            // Console.WriteLine($"px:{px},py:{py}");
            // Console.WriteLine(pictureBox1.Location);
            // Console.WriteLine($"{pictureBox1.Width-width},{pictureBox1.Height-height}");
        }
        else
        {
            pictureBox1.Height = (int)(pictureBox1.Height / step);
            pictureBox1.Width = (int)(pictureBox1.Width / step);
            int x = (int)(e.X * (1 - 1 / step));
            int y = (int)(e.Y * (1 - 1 / step));
            pictureBox1.Location = new Point(pictureBox1.Location.X + x, pictureBox1.Location.Y + y);
        }
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        this._engin.Dispose();
    }

    private bool CheckPoint(Point p, List<OCRPoint> ocrPoints)
    {
        bool flag = false;

        if (p.X < ocrPoints.Select(t => t.X).Min() ||
            p.X > ocrPoints.Select(t => t.X).Max() ||
            p.Y < ocrPoints.Select(t => t.Y).Min() ||
            p.Y > ocrPoints.Select(t => t.Y).Max())
        {
            return false;
        }

        for (int i = 0, l = ocrPoints.Count, j = l - 1; i < ocrPoints.Count; j = i, i++)
        {
            Point pi = new Point(ocrPoints[i].X, ocrPoints[i].Y);
            Point pj = new Point(ocrPoints[j].X, ocrPoints[j].Y);
            if (p.Equals(pi) || p.Equals(pj))
            {
                return true;
            }

            if (pj.Y == pi.Y)
            {
                continue;
            }

            if ((p.Y >= pj.Y && p.Y > pi.Y) || (p.Y < pi.Y && p.Y < pj.Y))
            {
                continue;
            }

            int x = pj.X + (p.Y - pj.Y) * ((pi.X - pj.X) / (pi.Y - pj.Y));
            if (x < p.X)
            {
                flag = !flag;
            }
        }

        return flag;
    }
}